<?php

assert_options(ASSERT_ACTIVE,     1);
assert_options(ASSERT_WARNING,    0);
assert_options(ASSERT_BAIL,       0);
assert_options(ASSERT_QUIET_EVAL, 0);
assert_options(ASSERT_CALLBACK,   'SweetTestSuite::assertCallBack');

class SweetTestSuite
{
	/**
	 * Classes to be Tests
	 *
	 * @var array(ReflectionClass)
	 */
	private $classes = array();
	
	/**
	 * Failed Tests i.e. Methods
	 *
	 * @var array
	 */
	private $failed = array();
	
	/**
	 * Passed Tests i.e. Methods
	 *
	 * @var array
	 */
	private $passed = array();
	
	/**
	 * Errored Tests i.e. Methods
	 *
	 * @var array
	 */
	private $errored = array();
	
	/**
	 * Static assert catcher so it doesnt freak out
	 *
	 * @param string $file
	 * @param string $line
	 * @param string $message
	 */
	public static function assertCallBack($file, $line, $message)
	{
		$code = explode("\n",file_get_contents($file));
		$exception = new Exception();
		$exception->seMessage = $message;
		$exception->seLine = $line;
		$exception->seFile = $file;
		$exception->seCode = trim($code[$line-1]);
		throw $exception;
	}
	
	/**
	 * Total number of tests
	 *
	 * @return int
	 */
	public function getNumberOfTests()
	{
		$total = 0;
		foreach($this->classes as $class)
			$total += sizeof($class); 
		
		return $total;
	}
	
	/**
	 * Get Tests
	 *
	 * @return array
	 */
	public function getTests()
	{
		return $this->classes;
	}
	
	/**
	 * Number of tests Passed
	 *
	 * @return int
	 */
	public function getNumberOfPassedTests()
	{
		$total = 0;
		foreach($this->passed as $class)
			$total += sizeof($class); 
		
		return $total;
	}
	
	/**
	 * Passed Tests
	 * @return array
	 */
	public function getPassedTests()
	{
		return $this->passed;
	}
	
	/**
	 * Number of tests Failed
	 *
	 * @return int
	 */
	public function getNumberOfFailedTests()
	{
		$total = 0;
		foreach($this->failed as $class)
			$total += sizeof($class); 
		
		return $total;
	}

	/**
	 * Failed Tests
	 *
	 * @return array
	 */
	public function getFailedTests()
	{
		return $this->failed;
	}	
	
	/**
	 * Number of tests Errored
	 *
	 * @return int
	 */
	public function getNumberOfErroredTests()
	{
		$total = 0;
		foreach($this->errored as $class)
			$total += sizeof($class); 
		
		return $total;
	}
	
	/**
	 * Errored Tests
	 *
	 * @return array
	 */
	public function getErroredTests()
	{
		return $this->errored;
	}
	
	/**
	 * Adds a Test Class to the Suite
	 *
	 * @param string $className
	 * @return boolean
	 */
	public function addTestClass($className)
	{
		try {
			$class = new ReflectionClass($className);
		} catch (ReflectionException $e)
		{
			throw new Exception("Cannot find class named ".$className.".");
		}

		if(($parent = $class->getParentClass()) === false || $parent->getName() != "SweetTestClass")
			return false;
		
		$this->generateTests($class);
	}
	
	/**
	 * Runs all tests for a suite
	 *
	 * @param boolean $timeout
	 */
	public function run($timeout = false)
	{
		if(!$timeout)
		{
			$maxtimeout = ini_get("max_execution_time");
			set_time_limit(0);
		}
		
		foreach($this->classes as $className => $methods)
		{
			$instance = new $className();
			foreach($methods as $method)
			{
				if($timeout)
					echo ".";
					
				$methodName = $method->getName();
				try {
					$instance->$methodName();
				} catch(Exception $e)
				{
					if($e instanceof SweetException)
					{
						$this->failed[$className][$methodName] = $e;
						continue;
					} else {
						$this->errored[$className][$methodName]= $e;
						continue;
					}
				}
				$this->passed[$className][$methodName] = true;
			}
		}
		
		if(!$timeout)
		{
			set_time_limit($maxtimeout);
		}
	}
	
	/**
	 * Generates tests for a class
	 *
	 * @param ReflectionClass $class
	 */
	private function generateTests(ReflectionClass $class)
	{
		$className = $class->getName();
		if(isset($this->classes[$className]))
		{
			throw new Exception("Class: ".$className." already added for testing.");
		} else {
			$methods = $class->getMethods();
			foreach($methods as $method)
			{
				if($method->getDeclaringClass() == $class)
					$this->classes[$className][] = $method;
			}
			
		}		
	}
	
	/**
	 * toString
	 *
	 * @return string
	 */
	public function __toString()
	{
		$string = "Tests: ".$this->getNumberOfPassedTests()."/".$this->getNumberOfTests()." Failed: ".$this->getNumberOfFailedTests()." Errored: ".$this->getNumberOfErroredTests()."\n";
		
		foreach($this->passed as $class => $methods)
			foreach($methods as $method => $value)
				$string .= "PASSED : ".$class."::".$method."\n";
		 
		foreach($this->failed as $class => $methods)
			foreach($methods as $method => $exception)
				$string .= "FAILED : ".$class."::".$method." ".$exception."\n";		
				
		foreach($this->errored as $class => $methods)
			foreach($methods as $method => $exception)
				$string .= "ERRORED: ".$class."::".$method." ".$exception->getMessage()."\n";				
		return $string;
	}
}

?>